/*
cAndy Apple Core: An Apple //e emulator engine
    - based almost entirely on AppleWin

Copyright (C) 1994-1996, Michael O'Brien
Copyright (C) 1999-2001, Oliver Schmidt
Copyright (C) 2002-2005, Tom Charlesworth
Copyright (C) 2006-2008, Tom Charlesworth, Michael Pohoreski, Nick Westgate
Copyright (C) 2009-2010, Garnet Ulrich

cAndy Apple Core is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

cAndy Apple Core is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with cAndy Apple Core; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "StdAfx.h"

#include "wwrapper.h"

//#include <android/log.h>

//static char longvar[30];

extern BYTE * pCARom;
extern eApple2Type CARomType;

static BOOL CAMemExists = 0;

// Memory Flag
#define  MF_80STORE    0x00000001
#define  MF_ALTZP      0x00000002
#define  MF_AUXREAD    0x00000004
#define  MF_AUXWRITE   0x00000008
#define  MF_BANK2      0x00000010
#define  MF_HIGHRAM    0x00000020
#define  MF_HIRES      0x00000040
#define  MF_PAGE2      0x00000080
#define  MF_SLOTC3ROM  0x00000100
#define  MF_SLOTCXROM  0x00000200
#define  MF_WRITERAM   0x00000400
#define  MF_IMAGEMASK  0x000003F7

#define  SW_80STORE    (memmode & MF_80STORE)
#define  SW_ALTZP      (memmode & MF_ALTZP)
#define  SW_AUXREAD    (memmode & MF_AUXREAD)
#define  SW_AUXWRITE   (memmode & MF_AUXWRITE)
#define  SW_BANK2      (memmode & MF_BANK2)
#define  SW_HIGHRAM    (memmode & MF_HIGHRAM)
#define  SW_HIRES      (memmode & MF_HIRES)
#define  SW_PAGE2      (memmode & MF_PAGE2)
#define  SW_SLOTC3ROM  (memmode & MF_SLOTC3ROM)
#define  SW_SLOTCXROM  (memmode & MF_SLOTCXROM)
#define  SW_WRITERAM   (memmode & MF_WRITERAM)

/*
MEMORY MANAGEMENT SOFT SWITCHES
 $C000   W       80STOREOFF      Allow page2 to switch video page1 page2
 $C001   W       80STOREON       Allow page2 to switch main & aux video memory
 $C002   W       RAMRDOFF        Read enable main memory from $0200-$BFFF
 $C003   W       RAMDRON         Read enable aux memory from $0200-$BFFF
 $C004   W       RAMWRTOFF       Write enable main memory from $0200-$BFFF
 $C005   W       RAMWRTON        Write enable aux memory from $0200-$BFFF
 $C006   W       INTCXROMOFF     Enable slot ROM from $C100-$CFFF
 $C007   W       INTCXROMON      Enable main ROM from $C100-$CFFF
 $C008   W       ALZTPOFF        Enable main memory from $0000-$01FF & avl BSR
 $C009   W       ALTZPON         Enable aux memory from $0000-$01FF & avl BSR
 $C00A   W       SLOTC3ROMOFF    Enable main ROM from $C300-$C3FF
 $C00B   W       SLOTC3ROMON     Enable slot ROM from $C300-$C3FF

VIDEO SOFT SWITCHES
 $C00C   W       80COLOFF        Turn off 80 column display
 $C00D   W       80COLON         Turn on 80 column display
 $C00E   W       ALTCHARSETOFF   Turn off alternate characters
 $C00F   W       ALTCHARSETON    Turn on alternate characters
 $C050   R/W     TEXTOFF         Select graphics mode
 $C051   R/W     TEXTON          Select text mode
 $C052   R/W     MIXEDOFF        Use full screen for graphics
 $C053   R/W     MIXEDON         Use graphics with 4 lines of text
 $C054   R/W     PAGE2OFF        Select panel display (or main video memory)
 $C055   R/W     PAGE2ON         Select page2 display (or aux video memory)
 $C056   R/W     HIRESOFF        Select low resolution graphics
 $C057   R/W     HIRESON         Select high resolution graphics

SOFT SWITCH STATUS FLAGS
 $C010   R7      AKD             1=key pressed   0=keys free    (clears strobe)
 $C011   R7      BSRBANK2        1=bank2 available    0=bank1 available
 $C012   R7      BSRREADRAM      1=BSR active for read   0=$D000-$FFFF active
 $C013   R7      RAMRD           0=main $0200-$BFFF active reads  1=aux active
 $C014   R7      RAMWRT          0=main $0200-$BFFF active writes 1=aux writes
 $C015   R7      INTCXROM        1=main $C100-$CFFF ROM active   0=slot active
 $C016   R7      ALTZP           1=aux $0000-$1FF+auxBSR    0=main available
 $C017   R7      SLOTC3ROM       1=slot $C3 ROM active   0=main $C3 ROM active
 $C018   R7      80STORE         1=page2 switches main/aux   0=page2 video
 $C019   R7      VERTBLANK       1=vertical retrace on   0=vertical retrace off
 $C01A   R7      TEXT            1=text mode is active   0=graphics mode active
 $C01B   R7      MIXED           1=mixed graphics & text    0=full screen
 $C01C   R7      PAGE2           1=video page2 selected or aux
 $C01D   R7      HIRES           1=high resolution graphics   0=low resolution
 $C01E   R7      ALTCHARSET      1=alt character set on    0=alt char set off
 $C01F   R7      80COL           1=80 col display on     0=80 col display off
*/



//-----------------------------------------------------------------------------

static LPBYTE  memshadow[0x100];
LPBYTE         memwrite[0x100];

iofunction		IORead[256];
iofunction		IOWrite[256];
static LPVOID	SlotParameters[NUM_SLOTS];

static BOOL    lastwriteram = 0;

LPBYTE         mem          = NULL;

//

static LPBYTE  memaux       = NULL;
static LPBYTE  memmain      = NULL;

LPBYTE         memdirty     = NULL;
static LPBYTE  memrom       = NULL;

static LPBYTE  memimage     = NULL;

static LPBYTE	pCxRomInternal		= NULL;
static LPBYTE	pCxRomPeripheral	= NULL;

static DWORD   memmode      = MF_BANK2 | MF_SLOTCXROM | MF_WRITERAM;
static BOOL    modechanging = 0;

BYTE  IO_Annunciator(WORD programcounter, WORD address, BYTE write, BYTE value, ULONG nCycles);
static void UpdatePaging(BOOL initialize, BOOL updatewriteonly);

//DEFAULTROM is based on defaultrom from APPLE2GO java project
unsigned char DEFAULTROM[464] = { //1d0 bytes
        // Reset routine
        0xad,0x51,0xc0,    //gu - rom starts by setting text mode with this opcode AD c051 call
        0xa9,0xa0,

        0xa2,0xff,0x9d,0xff,0x03,0xca,0xd0,0xfa,
        0xa2,0xff,0x9d,0xff,0x04,0xca,0xd0,0xfa,
        0xa2,0xff,0x9d,0xff,0x05,0xca,0xd0,0xfa,
        0xa2,0xff,0x9d,0xff,0x06,0xca,0xd0,0xfa,

        0xa2,0x27,0xbd,0xb8,0xfe,0x9d,0x80,0x04,0xca,0x10,0xf7,
        0xa2,0x27,0xbd,0xe0,0xfe,0x9d,0x00,0x05,0xca,0x10,0xf7,
        0xa2,0x27,0xbd,0x08,0xff,0x9d,0x80,0x05,0xca,0x10,0xf7,
        0xa2,0x27,0xbd,0x30,0xff,0x9d,0x00,0x06,0xca,0x10,0xf7,
        0xa2,0x27,0xbd,0x58,0xff,0x9d,0x80,0x07,0xca,0x10,0xf7,
        0xa2,0x27,0xbd,0x80,0xff,0x9d,0x28,0x04,0xca,0x10,0xf7,
        0xa2,0x27,0xbd,0xa8,0xff,0x9d,0x00,0x07,0xca,0x10,0xf7,
        0xa2,0x27,0xbd,0xd0,0xff,0x9d,0x00,0x07,0xca,0x10,0xf7,

        0x4c,0xad,0xfe,
        0xea,0xea,0xea,0xea,0xea,0xea,0xea,0xea,

        // Text message
        0xa0,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
        0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
        0x20,0x20,0x20,0x20,0x20,0x20,0x20,0xa0,

        0xa0,0x20,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xe3,0xc1,0xee,0xe4,0xf9,0xa0,0xc1,0xf0,
        0xf0,0xec,0xe5,0xa0,0xf2,0xe5,0xf1,0xf5,0xe9,0xf2,0xe5,0xf3,0xa0,0xe1,0xee,0xa0,
        0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0x20,0xa0,    // cAndy Apple requires an

        0xa0,0x20,0xa0,0xa0,0xa0,0xa0,0xa0,0xc1,0xf0,0xf0,0xec,0xe5,0xa0,0xaf,0xaf,0xe5,
        0xa0,0xd2,0xcf,0xcd,0xa0,0xe9,0xed,0xe1,0xe7,0xe5,0xa0,0xf4,0xef,0xa0,0xf2,0xf5,
        0xee,0xa0,0xa0,0xa0,0xa0,0xa0,0x20,0xa0,    // Apple //e ROM image to run

        0xa0,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
        0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
        0x20,0x20,0x20,0x20,0x20,0x20,0x20,0xa0,

        0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xce,0xef,0xf4,0xe5,0xba,0xa0,0xd9,0xef,0xf5,0xa0,
        0xed,0xe1,0xf9,0xa0,0xec,0xe5,0xe7,0xe1,0xec,0xec,0xf9,0xa0,0xe4,0xef,0xa0,0xf3,
        0xef,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0, // Note: You may legally do so

        0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xe9,0xe6,0xa0,0xf9,0xef,0xf5,0xa0,0xef,
        0xf7,0xee,0xa0,0xe1,0xee,0xa0,0xc1,0xf0,0xf0,0xec,0xe5,0xa0,0xaf,0xaf,0xe5,0xae,
        0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0, // if you own an Apple //e.

        0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,
        0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,
        0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,

        0xa0,0xa0,0xc8,0xe9,0xf4,0xa0,0xcd,0xc5,0xce,0xd5,0xac,0xa0,0xa7,0xc4,0xef,0xf7,
        0xee,0xec,0xef,0xe1,0xe4,0xa0,0xd2,0xcf,0xcd,0xa7,0xa0,0xf4,0xef,0xa0,0xe7,0xe5,
        0xf4,0xa0,0xef,0xee,0xe5,0xae,0xa0,0xa0, // Hit MENU, Download ROM to get one.

        // Interrupt vectors
        0x00,0x00,0x30,0xfe,0x30,0xfe,0x30,0xfe
};

//=============================================================================

static BYTE  IORead_C00x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
	return KeybReadData(pc, addr, bWrite, d, nCyclesLeft);
}

static BYTE  IOWrite_C00x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
	if ((addr & 0xf) <= 0xB)
		return MemSetPaging(pc, addr, bWrite, d, nCyclesLeft);
	else
		return VideoSetMode(pc, addr, bWrite, d, nCyclesLeft);
}

//-------------------------------------

static BYTE  IORead_C01x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
	switch (addr & 0xf)
	{
	case 0x0:	return KeybReadFlag(pc, addr, bWrite, d, nCyclesLeft);
	case 0x1:	return MemCheckPaging(pc, addr, bWrite, d, nCyclesLeft);
	case 0x2:	return MemCheckPaging(pc, addr, bWrite, d, nCyclesLeft);
	case 0x3:	return MemCheckPaging(pc, addr, bWrite, d, nCyclesLeft);
	case 0x4:	return MemCheckPaging(pc, addr, bWrite, d, nCyclesLeft);
	case 0x5:	return MemCheckPaging(pc, addr, bWrite, d, nCyclesLeft);
	case 0x6:	return MemCheckPaging(pc, addr, bWrite, d, nCyclesLeft);
	case 0x7:	return MemCheckPaging(pc, addr, bWrite, d, nCyclesLeft);
	case 0x8:	return MemCheckPaging(pc, addr, bWrite, d, nCyclesLeft);
	case 0x9:	return VideoCheckVbl(pc, addr, bWrite, d, nCyclesLeft);
	case 0xA:	return VideoCheckMode(pc, addr, bWrite, d, nCyclesLeft);
	case 0xB:	return VideoCheckMode(pc, addr, bWrite, d, nCyclesLeft);
	case 0xC:	return MemCheckPaging(pc, addr, bWrite, d, nCyclesLeft);
	case 0xD:	return MemCheckPaging(pc, addr, bWrite, d, nCyclesLeft);
	case 0xE:	return VideoCheckMode(pc, addr, bWrite, d, nCyclesLeft);
	case 0xF:	return VideoCheckMode(pc, addr, bWrite, d, nCyclesLeft);
	}

	return 0;
}

static BYTE  IOWrite_C01x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
	return KeybReadFlag(pc, addr, bWrite, d, nCyclesLeft);
}

//-------------------------------------

static BYTE  IORead_C02x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
}

static BYTE  IOWrite_C02x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
}

//-------------------------------------

static BYTE  IORead_C03x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
	return SpkrToggle(pc, addr, bWrite, d, nCyclesLeft);
}

static BYTE  IOWrite_C03x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
	return SpkrToggle(pc, addr, bWrite, d, nCyclesLeft);
}

//-------------------------------------

static BYTE  IORead_C04x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
}

static BYTE  IOWrite_C04x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
}

//-------------------------------------

static BYTE  IORead_C05x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
	switch (addr & 0xf)
	{
	case 0x0:	return VideoSetMode(pc, addr, bWrite, d, nCyclesLeft);
	case 0x1:	return VideoSetMode(pc, addr, bWrite, d, nCyclesLeft);
	case 0x2:	return VideoSetMode(pc, addr, bWrite, d, nCyclesLeft);
	case 0x3:	return VideoSetMode(pc, addr, bWrite, d, nCyclesLeft);
	case 0x4:	return MemSetPaging(pc, addr, bWrite, d, nCyclesLeft);
	case 0x5:	return MemSetPaging(pc, addr, bWrite, d, nCyclesLeft);
	case 0x6:	return MemSetPaging(pc, addr, bWrite, d, nCyclesLeft);
	case 0x7:	return MemSetPaging(pc, addr, bWrite, d, nCyclesLeft);
	case 0x8:	return IO_Annunciator(pc, addr, bWrite, d, nCyclesLeft);
	case 0x9:	return IO_Annunciator(pc, addr, bWrite, d, nCyclesLeft);
	case 0xA:	return IO_Annunciator(pc, addr, bWrite, d, nCyclesLeft);
	case 0xB:	return IO_Annunciator(pc, addr, bWrite, d, nCyclesLeft);
	case 0xC:	return IO_Annunciator(pc, addr, bWrite, d, nCyclesLeft);
	case 0xD:	return IO_Annunciator(pc, addr, bWrite, d, nCyclesLeft);
	case 0xE:	return VideoSetMode(pc, addr, bWrite, d, nCyclesLeft);
	case 0xF:	return VideoSetMode(pc, addr, bWrite, d, nCyclesLeft);
	}

	return 0;
}

static BYTE  IOWrite_C05x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
	switch (addr & 0xf)
	{
	case 0x0:	return VideoSetMode(pc, addr, bWrite, d, nCyclesLeft);
	case 0x1:	return VideoSetMode(pc, addr, bWrite, d, nCyclesLeft);
	case 0x2:	return VideoSetMode(pc, addr, bWrite, d, nCyclesLeft);
	case 0x3:	return VideoSetMode(pc, addr, bWrite, d, nCyclesLeft);
	case 0x4:	return MemSetPaging(pc, addr, bWrite, d, nCyclesLeft);
	case 0x5:	return MemSetPaging(pc, addr, bWrite, d, nCyclesLeft);
	case 0x6:	return MemSetPaging(pc, addr, bWrite, d, nCyclesLeft);
	case 0x7:	return MemSetPaging(pc, addr, bWrite, d, nCyclesLeft);
	case 0x8:	return IO_Annunciator(pc, addr, bWrite, d, nCyclesLeft);
	case 0x9:	return IO_Annunciator(pc, addr, bWrite, d, nCyclesLeft);
	case 0xA:	return IO_Annunciator(pc, addr, bWrite, d, nCyclesLeft);
	case 0xB:	return IO_Annunciator(pc, addr, bWrite, d, nCyclesLeft);
	case 0xC:	return IO_Annunciator(pc, addr, bWrite, d, nCyclesLeft);
	case 0xD:	return IO_Annunciator(pc, addr, bWrite, d, nCyclesLeft);
	case 0xE:	return VideoSetMode(pc, addr, bWrite, d, nCyclesLeft);
	case 0xF:	return VideoSetMode(pc, addr, bWrite, d, nCyclesLeft);
	}

	return 0;
}

//-------------------------------------

static BYTE  IORead_C06x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
	static BYTE CurrentKestroke = 0;
	CurrentKestroke = KeybGetKeycode();
	switch (addr & 0xf)
	{

	//case 0x0:	return TapeRead(pc, addr, bWrite, d, nCyclesLeft); //gu - taking out taperead
	case 0x0:   return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	case 0x1:	return JoyReadButton(pc, addr, bWrite, d, nCyclesLeft);  //$C061 Digital input 0 (If bit 7=1 then JoyButton 0 or OpenApple is pressed)
	case 0x2:	return JoyReadButton(pc, addr, bWrite, d, nCyclesLeft);  //$C062 Digital input 1 (If bit 7=1 then JoyButton 1 or ClosedApple is pressed)
	case 0x3:	return JoyReadButton(pc, addr, bWrite, d, nCyclesLeft);  //$C063 Digital input 2
	case 0x4:	return JoyReadPosition(pc, addr, bWrite, d, nCyclesLeft); //$C064 Analog input 0
	case 0x5:	return JoyReadPosition(pc, addr, bWrite, d, nCyclesLeft); //$C065 Analog input 1
	case 0x6:	return JoyReadPosition(pc, addr, bWrite, d, nCyclesLeft); //$C066 Analog input 2
	case 0x7:	return JoyReadPosition(pc, addr, bWrite, d, nCyclesLeft); //$C067 Analog input 3
	case 0x8:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	case 0x9:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	case 0xA:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	case 0xB:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	case 0xC:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	case 0xD:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	case 0xE:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	case 0xF:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	}

	return 0;
}

static BYTE  IOWrite_C06x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
	return IO_Null(pc, addr, bWrite, d, nCyclesLeft); //Apple2 value
}

//-------------------------------------

static BYTE  IORead_C07x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
	switch (addr & 0xf)
	{
	case 0x0:	return JoyResetPosition(pc, addr, bWrite, d, nCyclesLeft);  //$C070 Analog input reset
	case 0x1:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	case 0x2:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	case 0x3:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	case 0x4:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	case 0x5:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	case 0x6:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	case 0x7:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	case 0x8:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	case 0x9:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	case 0xA:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	case 0xB:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	case 0xC:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	case 0xD:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	case 0xE:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
	case 0xF:	return VideoCheckMode(pc, addr, bWrite, d, nCyclesLeft);
	}

	return 0;
}

static BYTE  IOWrite_C07x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
    switch (addr & 0xf)
    {
    case 0x0:	return JoyResetPosition(pc, addr, bWrite, d, nCyclesLeft);
    case 0x1:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
    case 0x2:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
    case 0x3:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
    case 0x4:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
    case 0x5:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
    case 0x6:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
    case 0x7:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
    case 0x8:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
    case 0x9:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
    case 0xA:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
    case 0xB:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
    case 0xC:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
    //	case 0xD:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft);
    case 0xD:	// No Slot Clock Hack for ProDOS
	    return IO_Null(pc, addr, bWrite, d, nCyclesLeft);

	//http://www.1000bit.net/support/manuali/apple/technotes/aiie/tn.aiie.10.html
	//IOUDIS (W): $C07E
	//    There is no IOU to disable
	//IOUDIS (W): $C07F
	//    There is no IOU to enable
	//RDIOUDIS (R7): $C07E
	//    There is no IOUDIS switch to read
	case 0xE:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft); // TODO: IOUDIS
	case 0xF:	return IO_Null(pc, addr, bWrite, d, nCyclesLeft); // TODO: IOUDIS
	}

	return 0;
}

//-----------------------------------------------------------------------------

static iofunction IORead_C0xx[8] =
{
	IORead_C00x,		// Keyboard
	IORead_C01x,		// Memory/Video
	IORead_C02x,		// Cassette
	IORead_C03x,		// Speaker
	IORead_C04x,
	IORead_C05x,		// Video
	IORead_C06x,		// Joystick
	IORead_C07x,		// Joystick/Video
};

static iofunction IOWrite_C0xx[8] =
{
	IOWrite_C00x,		// Memory/Video
	IOWrite_C01x,		// Keyboard
	IOWrite_C02x,		// Cassette
	IOWrite_C03x,		// Speaker
	IOWrite_C04x,
	IOWrite_C05x,		// Video/Memory
	IOWrite_C06x,
	IOWrite_C07x,		// Joystick/Ramworks
};

static BYTE IO_SELECT;
static BYTE IO_SELECT_InternalROM;

static BYTE* ExpansionRom[NUM_SLOTS];

enum eExpansionRomType {eExpRomNull=0, eExpRomInternal, eExpRomPeripheral};
static eExpansionRomType g_eExpansionRomType = eExpRomNull;
static UINT	g_uPeripheralRomSlot = 0;

//=============================================================================

BYTE  IO_Null(WORD programcounter, WORD address, BYTE write, BYTE value, ULONG nCyclesLeft)
{
    if (!write) {
        return MemReadFloatingBus(nCyclesLeft);
    }
    else {
        return 0;
    }
}

BYTE  IO_Annunciator(WORD programcounter, WORD address, BYTE write, BYTE value, ULONG nCyclesLeft)
{
	// Apple//e ROM:
	// . PC=FA6F: LDA $C058 (SETAN0)
	// . PC=FA72: LDA $C05A (SETAN1)
	// . PC=C2B5: LDA $C05D (CLRAN2)

	// NB. AN3: For //e & //c these locations are now used to enabled/disabled DHIRES
	if (!write)
		return MemReadFloatingBus(nCyclesLeft);
	else
		return 0;
}

// Enabling expansion ROM ($C800..$CFFF]:
// . Enable if: Enable1 && Enable2
// . Enable1 = I/O SELECT' (6502 accesses $Csxx)
//   - Reset when 6502 accesses $CFFF
// . Enable2 = I/O STROBE' (6502 accesses [$C800..$CFFF])

BYTE  IORead_Cxxx(WORD programcounter, WORD address, BYTE write, BYTE value, ULONG nCyclesLeft)
{
    if (address == 0xCFFF)
    {
        // Disable expansion ROM at [$C800..$CFFF]
        // . SSC will disable on an access to $CFxx - but ROM only writes to $CFFF, so it doesn't matter
        IO_SELECT = 0;
        IO_SELECT_InternalROM = 0;
        g_uPeripheralRomSlot = 0;

        if (SW_SLOTCXROM)
        {
            // NB. SW_SLOTCXROM==0 ensures that internal rom stays switched in
            memset(pCxRomPeripheral+0x800, 0, FIRMWARE_EXPANSION_SIZE);
            memset(mem+FIRMWARE_EXPANSION_BEGIN, 0, FIRMWARE_EXPANSION_SIZE);
            g_eExpansionRomType = eExpRomNull;
        }

        // NB. IO_SELECT won't get set, so ROM won't be switched back in...
    }

    //

    BYTE IO_STROBE = 0;

    if (IS_APPLE2 || SW_SLOTCXROM)
    {
        if ((address >= APPLE_SLOT_BEGIN) && (address <= APPLE_SLOT_END))
        {
            const UINT uSlot = (address >> 8) & 0xF;
            if ((uSlot != 3) && ExpansionRom[uSlot])
                IO_SELECT |= 1<<uSlot;
            else if ((SW_SLOTC3ROM) && ExpansionRom[uSlot])
                IO_SELECT |= 1<<uSlot;		// Slot3 & Peripheral ROM
            else if (!SW_SLOTC3ROM)
                IO_SELECT_InternalROM = 1;	// Slot3 & Internal ROM
        }
        else if ((address >= FIRMWARE_EXPANSION_BEGIN) && (address <= FIRMWARE_EXPANSION_END))
        {
            IO_STROBE = 1;
        }

        //

        if (IO_SELECT && IO_STROBE)
        {
            // Enable Peripheral Expansion ROM
            UINT uSlot=1;
            for (; uSlot<NUM_SLOTS; uSlot++)
            {
                if (IO_SELECT & (1<<uSlot))
                {
                    BYTE RemainingSelected = IO_SELECT & ~(1<<uSlot);
                    break;
                }
            }

            if (ExpansionRom[uSlot] && (g_uPeripheralRomSlot != uSlot))
            {
                memcpy(pCxRomPeripheral+0x800, ExpansionRom[uSlot], FIRMWARE_EXPANSION_SIZE);
                memcpy(mem+FIRMWARE_EXPANSION_BEGIN, ExpansionRom[uSlot], FIRMWARE_EXPANSION_SIZE);
                g_eExpansionRomType = eExpRomPeripheral;
                g_uPeripheralRomSlot = uSlot;
            }
        }
        else if (IO_SELECT_InternalROM && IO_STROBE && (g_eExpansionRomType != eExpRomInternal))
        {
            // Enable Internal ROM
            // . Get this for PR#3
            memcpy(mem+FIRMWARE_EXPANSION_BEGIN, pCxRomInternal+0x800, FIRMWARE_EXPANSION_SIZE);
            g_eExpansionRomType = eExpRomInternal;
            g_uPeripheralRomSlot = 0;
        }
    }

    if (!IS_APPLE2 && !SW_SLOTCXROM)
    {
        // !SW_SLOTC3ROM = Internal ROM: $C300-C3FF
        // !SW_SLOTCXROM = Internal ROM: $C100-CFFF

        if ((address >= APPLE_SLOT_BEGIN) && (address <= APPLE_SLOT_END))	// Don't care about state of SW_SLOTC3ROM
            IO_SELECT_InternalROM = 1;
        else if ((address >= FIRMWARE_EXPANSION_BEGIN) && (address <= FIRMWARE_EXPANSION_END))
            IO_STROBE = 1;

        if (!SW_SLOTCXROM && IO_SELECT_InternalROM && IO_STROBE && (g_eExpansionRomType != eExpRomInternal))
        {
            // Enable Internal ROM
            memcpy(mem+FIRMWARE_EXPANSION_BEGIN, pCxRomInternal+0x800, FIRMWARE_EXPANSION_SIZE);
            g_eExpansionRomType = eExpRomInternal;
            g_uPeripheralRomSlot = 0;
        }
    }

    if ((g_eExpansionRomType == eExpRomNull) && (address >= FIRMWARE_EXPANSION_BEGIN))
        return IO_Null(programcounter, address, write, value, nCyclesLeft);
    else
        return mem[address];
}

BYTE  IOWrite_Cxxx(WORD programcounter, WORD address, BYTE write, BYTE value, ULONG nCyclesLeft)
{
	return 0;
}

//===========================================================================

static BYTE g_bmSlotInit = 0;

static void InitIoHandlers()
{
    g_bmSlotInit = 0;
    UINT i=0;

    for (; i<8; i++)	// C00x..C07x
    {
        IORead[i]	= IORead_C0xx[i];
        IOWrite[i]	= IOWrite_C0xx[i];
    }

    for (; i<16; i++)	// C08x..C0Fx
    {
        IORead[i]	= IO_Null;
        IOWrite[i]	= IO_Null;
    }

	for (; i<256; i++)	// C10x..CFFx
	{
		IORead[i]	= IORead_Cxxx;
		IOWrite[i]	= IOWrite_Cxxx;
	}


	IO_SELECT = 0;
	IO_SELECT_InternalROM = 0;
	g_eExpansionRomType = eExpRomNull;
	g_uPeripheralRomSlot = 0;

	for (i=0; i<NUM_SLOTS; i++)
		ExpansionRom[i] = NULL;
}

// All slots [0..7] must register their handlers
void RegisterIoHandler(UINT uSlot, iofunction IOReadC0, iofunction IOWriteC0, iofunction IOReadCx, iofunction IOWriteCx, LPVOID lpSlotParameter, BYTE* pExpansionRom)
{
	g_bmSlotInit |= 1<<uSlot;
	SlotParameters[uSlot] = lpSlotParameter;

	IORead[uSlot+8]		= IOReadC0;
	IOWrite[uSlot+8]	= IOWriteC0;

	if (uSlot == 0)		// Don't trash C0xx handlers
		return;

	if (IOReadCx == NULL)	IOReadCx = IORead_Cxxx;
	if (IOWriteCx == NULL)	IOWriteCx = IOWrite_Cxxx;

	for (UINT i=0; i<16; i++)
	{
		IORead[uSlot*16+i]	= IOReadCx;
		IOWrite[uSlot*16+i]	= IOWriteCx;
	}

	// What about [$C80x..$CFEx]? - Do any cards use this as I/O memory?
	ExpansionRom[uSlot] = pExpansionRom;
}


//===========================================================================

void ResetPaging (BOOL initialize)
{
	lastwriteram = 0;
	memmode      = MF_BANK2 | MF_SLOTCXROM | MF_WRITERAM;
	UpdatePaging(initialize, 0);
}

//===========================================================================

static void UpdatePaging (BOOL initialize, BOOL updatewriteonly)
{
	// SAVE THE CURRENT PAGING SHADOW TABLE
	LPBYTE oldshadow[256];
	if (!(initialize || updatewriteonly /*|| fastpaging*/ ))
		CopyMemory(oldshadow,memshadow,256*sizeof(LPBYTE));

	// UPDATE THE PAGING TABLES BASED ON THE NEW PAGING SWITCH VALUES
	UINT loop;
	if (initialize)
	{
		for (loop = 0x00; loop < 0xC0; loop++)
			memwrite[loop] = mem+(loop << 8);

		for (loop = 0xC0; loop < 0xD0; loop++)
			memwrite[loop] = NULL;
	}

	if (!updatewriteonly)
	{
		for (loop = 0x00; loop < 0x02; loop++)
			memshadow[loop] = SW_ALTZP ? memaux+(loop << 8) : memmain+(loop << 8);
	}

	for (loop = 0x02; loop < 0xC0; loop++)
	{
		memshadow[loop] = SW_AUXREAD ? memaux+(loop << 8)
			: memmain+(loop << 8);

		memwrite[loop]  = ((SW_AUXREAD != 0) == (SW_AUXWRITE != 0))
			? mem+(loop << 8)
			: SW_AUXWRITE	? memaux+(loop << 8)
							: memmain+(loop << 8);
	}

	if (!updatewriteonly)
	{
		for (loop = 0xC0; loop < 0xC8; loop++)
		{
			const UINT uSlotOffset = (loop & 0x0f) * 0x100;
			if (loop == 0xC3)
				memshadow[loop] = (SW_SLOTC3ROM && SW_SLOTCXROM)	? pCxRomPeripheral+uSlotOffset	// C300..C3FF - Slot 3 ROM (all 0x00's)
																	: pCxRomInternal+uSlotOffset;	// C300..C3FF - Internal ROM
			else
				memshadow[loop] = SW_SLOTCXROM	? pCxRomPeripheral+uSlotOffset						// C000..C7FF - SSC/Disk][/etc
												: pCxRomInternal+uSlotOffset;						// C000..C7FF - Internal ROM
		}

		for (loop = 0xC8; loop < 0xD0; loop++)
		{
			const UINT uRomOffset = (loop & 0x0f) * 0x100;
			memshadow[loop] = pCxRomInternal+uRomOffset;											// C800..CFFF - Internal ROM
		}
	}

	for (loop = 0xD0; loop < 0xE0; loop++)
	{
		int bankoffset = (SW_BANK2 ? 0 : 0x1000);
		memshadow[loop] = SW_HIGHRAM ? SW_ALTZP	? memaux+(loop << 8)-bankoffset
												: memmain+(loop << 8)-bankoffset
									: memrom+((loop-0xD0) * 0x100);

		memwrite[loop]  = SW_WRITERAM	? SW_HIGHRAM	? mem+(loop << 8)
														: SW_ALTZP	? memaux+(loop << 8)-bankoffset
																	: memmain+(loop << 8)-bankoffset
										: NULL;
	}

	for (loop = 0xE0; loop < 0x100; loop++)
	{
		memshadow[loop] = SW_HIGHRAM	? SW_ALTZP	? memaux+(loop << 8)
													: memmain+(loop << 8)
										: memrom+((loop-0xD0) * 0x100);

		memwrite[loop]  = SW_WRITERAM	? SW_HIGHRAM	? mem+(loop << 8)
														: SW_ALTZP	? memaux+(loop << 8)
																	: memmain+(loop << 8)
										: NULL;
	}

	if (SW_80STORE)
	{
		for (loop = 0x04; loop < 0x08; loop++)
		{
			memshadow[loop] = SW_PAGE2	? memaux+(loop << 8)
										: memmain+(loop << 8);
			memwrite[loop]  = mem+(loop << 8);
		}

		if (SW_HIRES)
		{
			for (loop = 0x20; loop < 0x40; loop++)
			{
				memshadow[loop] = SW_PAGE2	? memaux+(loop << 8)
											: memmain+(loop << 8);
				memwrite[loop]  = mem+(loop << 8);
			}
		}
	}

	// MOVE MEMORY BACK AND FORTH AS NECESSARY BETWEEN THE SHADOW AREAS AND
	// THE MAIN RAM IMAGE TO KEEP BOTH SETS OF MEMORY CONSISTENT WITH THE NEW
	// PAGING SHADOW TABLE
	if (!updatewriteonly)
	{
		for (loop = 0x00; loop < 0x100; loop++)
		{
			if (initialize || (oldshadow[loop] != memshadow[loop]))
			{
				if ((!(initialize)) &&
					((*(memdirty+loop) & 1) || (loop <= 1)))
				{
					*(memdirty+loop) &= ~1;
					CopyMemory(oldshadow[loop],mem+(loop << 8),256);
				}

				CopyMemory(mem+(loop << 8),memshadow[loop],256);
			}
		}
	}

}

//
// ----- ALL GLOBALLY ACCESSIBLE FUNCTIONS ARE BELOW THIS LINE -----
//

//===========================================================================

// TODO: >= Apple2e only?
BYTE  MemCheckPaging (WORD, WORD address, BYTE, BYTE, ULONG)
{
	address &= 0xFF;
	BOOL result = 0;
	switch (address)
	{
	case 0x11: result = SW_BANK2;       break;
	case 0x12: result = SW_HIGHRAM;     break;
	case 0x13: result = SW_AUXREAD;     break;
	case 0x14: result = SW_AUXWRITE;    break;
	case 0x15: result = !SW_SLOTCXROM;  break;
	case 0x16: result = SW_ALTZP;       break;
	case 0x17: result = SW_SLOTC3ROM;   break;
	case 0x18: result = SW_80STORE;     break;
	case 0x1C: result = SW_PAGE2;       break;
	case 0x1D: result = SW_HIRES;       break;
	}
	return KeybGetKeycode() | (result ? 0x80 : 0);
}

//===========================================================================

bool MemGet80Store()
{
	return SW_80STORE != 0;
}

//===========================================================================
LPBYTE MemGetAuxPtr (WORD offset)
{
	LPBYTE lpMem = (memshadow[(offset >> 8)] == (memaux+(offset & 0xFF00)))
			? mem+offset
			: memaux+offset;

	return lpMem;
}

//===========================================================================
LPBYTE MemGetMainPtr (WORD offset)
{
  return (memshadow[(offset >> 8)] == (memmain+(offset & 0xFF00)))
           ? mem+offset
           : memmain+offset;
}

//===========================================================================

void MemPreInitialize ()
{
	// Init the I/O handlers
	InitIoHandlers();
}

//===========================================================================

void MemInitialize()
{

    const unsigned int _6502_MEM_END   = 0xFFFF;
    const UINT CxRomSize = 4*1024;
    const UINT Apple2RomSize = 12*1024; //2 is 12k rom - gu
    const UINT Apple2eRomSize = Apple2RomSize+CxRomSize; //2e is 16k rom - gu

    if (!CAMemExists) { //allocate the memory if it wasn't already done
        CAMemExists = 1;
        // ALLOCATE MEMORY FOR THE APPLE MEMORY IMAGE AND ASSOCIATED DATA STRUCTURES
        memaux   = (LPBYTE)VirtualAlloc(NULL,_6502_MEM_END+1,MEM_COMMIT,PAGE_READWRITE);
        memmain  = (LPBYTE)VirtualAlloc(NULL,_6502_MEM_END+1,MEM_COMMIT,PAGE_READWRITE);
        memdirty = (LPBYTE)VirtualAlloc(NULL,0x100  ,MEM_COMMIT,PAGE_READWRITE);
        memrom   = (LPBYTE)VirtualAlloc(NULL,0x5000 ,MEM_COMMIT,PAGE_READWRITE);
        // THE MEMIMAGE BUFFER CAN CONTAIN EITHER MULTIPLE MEMORY IMAGES OR ONE MEMORY IMAGE WITH COMPILER DATA
        memimage = (LPBYTE)VirtualAlloc(NULL,_6502_MEM_END+1,0x2000,0x0000); //gu - stuffed dummy values in last two to replace stupid MS crap

        pCxRomInternal		= (LPBYTE) VirtualAlloc(NULL, CxRomSize, MEM_COMMIT, PAGE_READWRITE);
        pCxRomPeripheral	= (LPBYTE) VirtualAlloc(NULL, CxRomSize, MEM_COMMIT, PAGE_READWRITE);
    }

	memset(pCxRomInternal,0,CxRomSize); // set to zeros - gu
	memset(pCxRomPeripheral,0,CxRomSize);

	if (CARomType == A2TYPE_STUB) { //gu - this var gets set by jniloaddefaultrom
	    memcpy(memrom + 0x3000 - 0x1d0, DEFAULTROM, 0x1d0);
	} else if (CARomType == A2TYPE_APPLE2EEHANCED) { //gu - this is the only rom we handle for now
		memcpy(pCxRomInternal, pCARom, CxRomSize);  //copy first 4k of CARom into cxrominternal
		memcpy(memrom, pCARom+0x1000, Apple2RomSize);			// ROM at $D000...$FFFF //next 12k of CARom goes into memrom - gu
	} else {
		memcpy(memrom, pCARom, Apple2RomSize);
	}

	const UINT uSlot = 0;
	RegisterIoHandler(uSlot, MemSetPaging, MemSetPaging, NULL, NULL, NULL, NULL);

	const UINT uSlot4 = 4;
	const UINT uSlot5 = 5;

	DiskLoadRom(pCxRomPeripheral, 6);				// $C600 : Disk][ f/w

	MemReset();
}

//===========================================================================

void MemReset ()
{
	// INITIALIZE THE PAGING TABLES
	ZeroMemory(memshadow,256*sizeof(LPBYTE));
	ZeroMemory(memwrite ,256*sizeof(LPBYTE));

	// INITIALIZE THE RAM IMAGES
	ZeroMemory(memaux ,0x10000);
	ZeroMemory(memmain,0x10000);

	int iByte;



	for( iByte = 0x0000; iByte < 0xC000; ) {
	    memmain[ iByte++ ] = 0xFF;
	    memmain[ iByte++ ] = 0xFF;

	    // Note: ODD 16-bit words are zero'd above...
	    iByte++;
	    iByte++;
	}

	// SET UP THE MEMORY IMAGE
	mem   = memimage;

	// INITIALIZE PAGING, FILLING IN THE 64K MEMORY IMAGE
	ResetPaging(1);

	// INITIALIZE & RESET THE CPU
	// . Do this after ROM has been copied back to mem[], so that PC is correctly init'ed from 6502's reset vector
	CpuInitialize();

}

//===========================================================================

void MemResetPaging ()
{
  ResetPaging(0);
}

//===========================================================================

// Called by Disk][ I/O only
BYTE MemReturnRandomData (BYTE highbit)
{
    static const BYTE retval[16] = {0x00,0x2D,0x2D,0x30,0x30,0x32,0x32,0x34,
            0x35,0x39,0x43,0x43,0x43,0x60,0x7F,0x7F};
    BYTE r = (BYTE)(rand() & 0xFF);

    if (r <= 170) {
        return 0x20 | (highbit ? 0x80 : 0);
    }
    else {
        return retval[r & 15] | (highbit ? 0x80 : 0);
    }
}

//===========================================================================

BYTE MemReadFloatingBus(const ULONG uExecutedCycles)
{
  return*(mem + VideoGetScannerAddress(NULL, uExecutedCycles)); //gu - removed (LPBYTE) just in case - probably not necessary
}

//===========================================================================

BYTE MemReadFloatingBus(const BYTE highbit, const ULONG uExecutedCycles)
{
  BYTE r = *(mem + VideoGetScannerAddress(NULL, uExecutedCycles));
  return (r & ~0x80) | ((highbit) ? 0x80 : 0);
}

//===========================================================================
BYTE  MemSetPaging (WORD programcounter, WORD address, BYTE write, BYTE value, ULONG nCyclesLeft)
{
  address &= 0xFF;
  DWORD lastmemmode = memmode;

  // DETERMINE THE NEW MEMORY PAGING MODE.
  if ((address >= 0x80) && (address <= 0x8F))
  {
    BOOL writeram = (address & 1);
    memmode &= ~(MF_BANK2 | MF_HIGHRAM | MF_WRITERAM);
    lastwriteram = 1; // note: because diags.do doesn't set switches twice!
    if (lastwriteram && writeram)
      memmode |= MF_WRITERAM;
    if (!(address & 8))
      memmode |= MF_BANK2;
    if (((address & 2) >> 1) == (address & 1))
      memmode |= MF_HIGHRAM;
    lastwriteram = writeram;
  }
  else if (!IS_APPLE2)
  {
    switch (address)
	{
		case 0x00: memmode &= ~MF_80STORE;    break;
		case 0x01: memmode |=  MF_80STORE;    break;
		case 0x02: memmode &= ~MF_AUXREAD;    break;
		case 0x03: memmode |=  MF_AUXREAD;    break;
		case 0x04: memmode &= ~MF_AUXWRITE;   break;
		case 0x05: memmode |=  MF_AUXWRITE;   break;
		case 0x06: memmode |=  MF_SLOTCXROM;  break;
		case 0x07: memmode &= ~MF_SLOTCXROM;  break;
		case 0x08: memmode &= ~MF_ALTZP;      break;
		case 0x09: memmode |=  MF_ALTZP;      break;
		case 0x0A: memmode &= ~MF_SLOTC3ROM;  break;
		case 0x0B: memmode |=  MF_SLOTC3ROM;  break;
		case 0x54: memmode &= ~MF_PAGE2;      break;
		case 0x55: memmode |=  MF_PAGE2;      break;
		case 0x56: memmode &= ~MF_HIRES;      break;
		case 0x57: memmode |=  MF_HIRES;      break;
	}
  }

  // IF THE EMULATED PROGRAM HAS JUST UPDATE THE MEMORY WRITE MODE AND IS
  // ABOUT TO UPDATE THE MEMORY READ MODE, HOLD OFF ON ANY PROCESSING UNTIL
  // IT DOES SO.

  if ((address >= 4) && (address <= 5) &&
     (((*(mem+programcounter)+(*(mem+programcounter+1)<<8)+(*(mem+programcounter+2)<<16)+(*(mem+programcounter+3)<<24)) & 0x00FFFEFF) == 0x00C0028D)) {
    modechanging = 1;
    return write ? 0 : MemReadFloatingBus(1, nCyclesLeft);
  }

  if ((address >= 0x80) && (address <= 0x8F) && (programcounter < 0xC000) &&
          ((((*(mem+programcounter)+(*(mem+programcounter+1)<<8)+(*(mem+programcounter+2)<<16)+(*(mem+programcounter+3)<<24)) & 0x00FFFEFF) == 0x00C0048D) ||
           (((*(mem+programcounter)+(*(mem+programcounter+1)<<8)+(*(mem+programcounter+2)<<16)+(*(mem+programcounter+3)<<24)) & 0x00FFFEFF) == 0x00C0028D))) {
    modechanging = 1;
    return write ? 0 : MemReadFloatingBus(1, nCyclesLeft);
  }

  // IF THE MEMORY PAGING MODE HAS CHANGED, UPDATE OUR MEMORY IMAGES AND
  // WRITE TABLES.
  if ((lastmemmode != memmode) || modechanging)
  {
    modechanging = 0;

	if ((lastmemmode & MF_SLOTCXROM) != (memmode & MF_SLOTCXROM))
	{
		if (SW_SLOTCXROM)
		{
			// Disable Internal ROM
			// . Similar to $CFFF access
			// . None of the peripheral cards can be driving the bus - so use the null ROM
			memset(pCxRomPeripheral+0x800, 0, FIRMWARE_EXPANSION_SIZE);
			memset(mem+0xC800, 0, 0x800);
			g_eExpansionRomType = eExpRomNull;
			g_uPeripheralRomSlot = 0;
		}
		else
		{
			// Enable Internal ROM
			memcpy(mem+0xC800, pCxRomInternal+0x800, FIRMWARE_EXPANSION_SIZE);
			g_eExpansionRomType = eExpRomInternal;
			g_uPeripheralRomSlot = 0;
		}
	}

      UpdatePaging(0,0);
  }

  if ((address <= 1) || ((address >= 0x54) && (address <= 0x57)))
    return VideoSetMode(programcounter,address,write,value,nCyclesLeft);

  return write ? 0 : MemReadFloatingBus(nCyclesLeft);
}

//===========================================================================

DWORD MemGetSnapshot(SS_BaseMemory* pSS)
{
	pSS->dwMemMode = memmode;
	pSS->bLastWriteRam = lastwriteram;

	for(DWORD dwOffset = 0x0000; dwOffset < 0x10000; dwOffset+=0x100)
	{
		memcpy(pSS->nMemMain+dwOffset, MemGetMainPtr((WORD)dwOffset), 0x100);
		memcpy(pSS->nMemAux+dwOffset, MemGetAuxPtr((WORD)dwOffset), 0x100);
	}

	return 0;
}

DWORD MemSetSnapshot(SS_BaseMemory* pSS)
{
	memmode = pSS->dwMemMode;
	lastwriteram = pSS->bLastWriteRam;

	memcpy(memmain, pSS->nMemMain, nMemMainSize);
	memcpy(memaux, pSS->nMemAux, nMemAuxSize);

	//

	modechanging = 0;

	UpdatePaging(1,0);		// Initialize=1, UpdateWriteOnly=0

	return 0;
}
